
using Sage.CloudStorage.Qiniu.Models;
using Sage.Data.Extensions;
using System.Text;

namespace Sage.CloudStorage.Qiniu.Auth
{
    /// <summary>
    /// 七牛云签名计算类
    /// 负责生成各种七牛云API所需的签名，支持多种签名算法
    /// </summary>
    /// <remarks>
    /// 七牛云使用HMAC-SHA1算法进行签名，不同的API场景使用不同的签名格式：
    /// 
    /// 1. **简单签名**：用于下载凭证等基础认证
    ///    - 直接对数据进行HMAC-SHA1签名
    ///    - 格式：AccessKey:签名
    /// 
    /// 2. **带数据签名**：用于上传凭证等需要携带策略的场景
    ///    - 对Base64编码后的数据进行签名
    ///    - 格式：AccessKey:签名:编码数据
    /// 
    /// 3. **HTTP请求签名**：用于管理API认证
    ///    - QBox格式：传统签名方式，包含URL路径和请求体
    ///    - Qiniu V2格式：新版签名，包含HTTP方法、Host等更多信息
    /// 
    /// 所有签名都使用URL安全的Base64编码，确保在HTTP传输中的安全性
    /// </remarks>
    /// <param name="qiniuKey">七牛云密钥信息，包含AccessKey和SecretKey</param>
    public class QiniuSignature(QiniuKey qiniuKey)
    {
        /// <summary>
        /// 七牛云密钥信息，用于签名计算
        /// 包含AccessKey（公开）和SecretKey（私密）
        /// </summary>
        private readonly QiniuKey _qiniuKey = qiniuKey ?? throw new ArgumentNullException(nameof(qiniuKey));

        /// <summary>
        /// 简单签名算法 - 用于下载凭证等基础认证场景
        /// </summary>
        /// <param name="data">待签名的原始数据（通常是URL或其他字符串）</param>
        /// <returns>签名结果，格式："AccessKey:HMAC_SHA1_签名"</returns>
        /// <exception cref="ArgumentException">当data为null或空时抛出</exception>
        /// <remarks>
        /// 签名流程：
        /// 1. 使用SecretKey对原始数据进行HMAC-SHA1计算
        /// 2. 将签名结果进行URL安全的Base64编码
        /// 3. 返回 "AccessKey:签名" 格式的字符串
        /// 
        /// 适用场景：
        /// - 私有文件的下载链接签名
        /// - 推流地址的签名验证
        /// - 其他简单的数据签名需求
        /// 
        /// 示例：
        /// Sign("https://example.com/file.jpg?e=1640995200")
        /// 返回：AccessKey:URL安全Base64编码的签名
        /// </remarks>
        public string Sign(string data)
        {
            ArgumentException.ThrowIfNullOrEmpty(data, nameof(data));

            // 使用扩展方法计算HMAC-SHA1签名并进行URL安全Base64编码
            var signature = data.ToHmacSha1(_qiniuKey.SecretKey).ToUrlSafeBase64();
            return $"{_qiniuKey.AccessKey}:{signature}";
        }

        /// <summary>
        /// 带数据签名算法 - 用于上传凭证等需要携带策略数据的场景
        /// </summary>
        /// <param name="data">待签名的数据，通常是JSON格式的上传策略</param>
        /// <returns>签名结果，格式："AccessKey:签名:Base64编码的原始数据"</returns>
        /// <exception cref="ArgumentException">当data为null或空时抛出</exception>
        /// <remarks>
        /// 签名流程：
        /// 1. 将原始数据转换为UTF-8字节数组
        /// 2. 对字节数组进行URL安全的Base64编码，得到encodedData
        /// 3. 使用SecretKey对encodedData进行HMAC-SHA1计算
        /// 4. 返回 "AccessKey:签名:encodedData" 格式的字符串
        /// 
        /// 适用场景：
        /// - 文件上传凭证生成（包含上传策略）
        /// - 流管理凭证生成
        /// - 其他需要携带完整数据的认证场景
        /// 
        /// 重要说明：
        /// - 返回的token中包含了原始数据的Base64编码
        /// - 客户端可以解码第三部分获取策略内容
        /// - 这种设计使得客户端可以了解上传限制和配置
        /// 
        /// 示例：
        /// SignWithData('{"scope":"bucket:key","deadline":1640995200}')
        /// 返回：AccessKey:签名:Base64编码的策略JSON
        /// </remarks>
        public string SignWithData(string data)
        {
            ArgumentException.ThrowIfNullOrEmpty(data, nameof(data));

            // 将字符串转换为UTF-8字节数组
            var dataBytes = Encoding.UTF8.GetBytes(data);

            // 使用扩展方法进行URL安全的Base64编码
            var encodedData = dataBytes.ToUrlSafeBase64();

            // 对编码后的数据进行HMAC-SHA1签名
            var signature = encodedData.ToHmacSha1(_qiniuKey.SecretKey).ToUrlSafeBase64();

            // 返回三段式格式：AccessKey:签名:编码数据
            return $"{_qiniuKey.AccessKey}:{signature}:{encodedData}";
        }

        /// <summary>
        /// HTTP请求签名算法 (QBox格式) - 用于传统管理API认证
        /// </summary>
        /// <param name="url">完整的请求URL，包含协议、域名、路径和查询参数</param>
        /// <param name="body">请求体的字节数组，GET请求通常为null</param>
        /// <returns>签名结果，格式："AccessKey:签名"，配合"QBox"认证头使用</returns>
        /// <exception cref="ArgumentNullException">当url为null时抛出</exception>
        /// <exception cref="ArgumentException">当url为空字符串时抛出</exception>
        /// <exception cref="UriFormatException">当url格式无效时抛出</exception>
        /// <remarks>
        /// QBox签名算法（七牛云传统格式）：
        /// 1. 从完整URL中提取路径和查询字符串部分（PathAndQuery）
        /// 2. 构建签名字符串：&lt;PathAndQuery&gt;\n&lt;Body&gt;
        /// 3. 使用SecretKey对签名字符串进行HMAC-SHA1计算
        /// 4. 对签名结果进行URL安全的Base64编码
        /// 5. 返回 "AccessKey:签名" 格式
        /// 
        /// 请求头格式：Authorization: QBox AccessKey:签名
        /// 
        /// 适用场景：
        /// - 存储桶管理API（创建、删除、列举等）
        /// - 文件管理API（删除、移动、复制等）
        /// - 统计查询API
        /// - 早期版本的七牛云API
        /// 
        /// 签名字符串示例：
        /// URL: https://uc.qiniuapi.com/buckets?tagCondition=
        /// 签名字符串: "/buckets?tagCondition=\n"
        /// 
        /// 带请求体的示例：
        /// URL: https://rs.qiniuapi.com/delete/encoded_entry_uri
        /// 请求体: {"key": "file.jpg"}
        /// 签名字符串: "/delete/encoded_entry_uri\n{\"key\": \"file.jpg\"}"
        /// 
        /// 注意事项：
        /// - 路径和查询参数之间必须有换行符\n
        /// - 请求体可以为空，但换行符必须存在
        /// - URL必须是完整的，包含协议和域名
        /// </remarks>
        public string SignRequest(string url, byte[]? body = null)
        {
            ArgumentException.ThrowIfNullOrEmpty(url, nameof(url));

            var uri = new Uri(url);
            // 获取URL的路径和查询参数部分（不包含域名）
            // 例如：/buckets?tagCondition= 或 /delete/encoded_entry_uri
            var pathAndQuery = uri.PathAndQuery;

            // 使用内存流构建待签名的数据，避免字符串拼接的性能开销
            using var buffer = new MemoryStream();

            // 1. 写入路径和查询参数的UTF-8字节
            var pathBytes = Encoding.UTF8.GetBytes(pathAndQuery);
            buffer.Write(pathBytes);

            // 2. 写入换行符（重要：QBox格式要求必须有换行符分隔）
            buffer.WriteByte((byte)'\n');

            // 3. 如果有请求体，写入请求体字节
            if (body != null && body.Length > 0)
            {
                buffer.Write(body);
            }

            // 4. 获取完整的待签名数据并计算HMAC-SHA1签名
            var bufferArray = buffer.ToArray();
            var signature = bufferArray.ToHmacSha1(_qiniuKey.SecretKey).ToUrlSafeBase64();

            // 返回AccessKey:签名格式，用于QBox认证头
            return $"{_qiniuKey.AccessKey}:{signature}";
        }

        /// <summary>
        /// HTTP请求签名算法V2 (Qiniu格式) - 用于新版管理API认证（推荐）
        /// </summary>
        /// <param name="method">HTTP请求方法（GET、POST、PUT、DELETE等），大小写不敏感</param>
        /// <param name="url">完整的请求URL，包含协议、域名、路径和查询参数</param>
        /// <param name="headers">请求头字典，键为头部名称，值为头部值，可选参数</param>
        /// <param name="body">请求体字符串内容，默认为空字符串</param>
        /// <returns>签名结果，格式："AccessKey:签名"，配合"Qiniu"认证头使用</returns>
        /// <exception cref="ArgumentException">当method或url为null或空时抛出</exception>
        /// <remarks>
        /// Qiniu V2签名算法（七牛云管理凭证V2格式）：
        /// 
        /// 签名字符串构建规则：
        /// ```
        /// &lt;Method&gt; &lt;PathAndQuery&gt;\n
        /// Host: &lt;Host&gt;\n
        /// [Content-Type: &lt;ContentType&gt;\n]
        /// [X-Qiniu-&lt;key1&gt;: &lt;value1&gt;\n]
        /// [X-Qiniu-&lt;key2&gt;: &lt;value2&gt;\n]
        /// ...\n
        /// [&lt;RequestBody&gt;]
        /// ```
        /// 
        /// 具体步骤：
        /// 1. HTTP方法（大写）+ 空格 + 路径和查询参数 + 换行
        /// 2. Host: 主机名（不包含默认端口80/443）+ 换行
        /// 3. Content-Type: 内容类型（如果存在）+ 换行
        /// 4. X-Qiniu-开头的自定义头部（按字母顺序排列）+ 换行
        /// 5. 空行（额外的换行符，形成双换行分隔）
        /// 6. 请求体内容（仅当Content-Type存在且不是application/octet-stream时）
        /// 
        /// 请求头格式：Authorization: Qiniu AccessKey:签名
        /// 
        /// 适用场景：
        /// - 七牛云存储管理API（文件操作、空间管理等）
        /// - CDN管理API
        /// - 数据处理API
        /// - 其他支持V2签名的七牛云服务
        /// 
        /// 签名字符串格式示例：
        /// ```
        /// GET /buckets?tagCondition=
        /// Host: uc.qiniuapi.com
        /// Content-Type: application/x-www-form-urlencoded
        /// X-Qiniu-Date: 20240315T120000Z
        /// 
        /// request_body_content
        /// ```
        /// 
        /// 相比传统QBox格式的优势：
        /// - 包含更多请求信息，防篡改能力更强
        /// - 支持自定义头部签名验证（X-Qiniu-*）
        /// - 符合现代API安全标准和最佳实践
        /// - 更好的兼容性和扩展性
        /// 
        /// 注意事项：
        /// - HTTP方法会自动转换为大写
        /// - Host不包含默认端口（80/443）
        /// - X-Qiniu-头部按字母顺序排序
        /// - 二进制流（application/octet-stream）不包含请求体
        /// </remarks>
        public string SignRequestV2(string method, string url, IDictionary<string, string>? headers = null, string? body = default)
        {
            ArgumentException.ThrowIfNullOrEmpty(method, nameof(method));
            ArgumentException.ThrowIfNullOrEmpty(url, nameof(url));

            var uri = new Uri(url);
            var pathAndQuery = uri.PathAndQuery;
            var host = uri.Host;

            // 处理端口（默认80和443端口不需要添加到Host中）
            // 注释掉是因为七牛云API通常使用标准端口，不需要显式添加
            // 如果需要支持非标准端口，可以取消注释以下代码：
            // if (uri.Port != 80 && uri.Port != 443 && !uri.IsDefaultPort)
            // {
            //     host += ":" + uri.Port;
            // }

            // 构建签名字符串，使用StringBuilder提高性能
            var signingString = new StringBuilder();

            // 1. HTTP方法（转换为大写）和路径查询参数
            signingString.Append($"{method.ToUpper()} {pathAndQuery}\n");

            // 2. Host头部（必需）
            signingString.Append($"Host: {host}\n");

            // 3. Content-Type头部（如果存在）
            if (headers != null && headers.TryGetValue("Content-Type", out string? contentTypeValue))
            {
                signingString.Append($"Content-Type: {contentTypeValue}\n");
            }

            // 4. X-Qiniu-开头的自定义头部（按字母顺序排列）
            if (headers != null)
            {
                var qiniuHeaders = headers
                    .Where(h => h.Key.StartsWith("X-Qiniu-", StringComparison.OrdinalIgnoreCase) &&
                               h.Key.Length > 8) // 排除空的X-Qiniu-前缀
                    .OrderBy(h => h.Key, StringComparer.OrdinalIgnoreCase);

                foreach (var header in qiniuHeaders)
                {
                    signingString.Append($"{header.Key}: {header.Value}\n");
                }
            }

            // 5. 空行分隔符（重要：形成双换行符，分隔头部和请求体）
            signingString.Append('\n');

            // 6. 请求体内容（仅当Content-Type存在且不是二进制流时）
            string? contentType = null;
            headers?.TryGetValue("Content-Type", out contentType);
            if (!string.IsNullOrEmpty(contentType) && contentType != "application/octet-stream")
            {
                // 只有在有Content-Type且不是二进制流时才包含请求体
                signingString.Append(body ?? string.Empty);
            }

            // 计算签名并返回
            var signatureData = signingString.ToString();
            var signature = signatureData.ToHmacSha1(_qiniuKey.SecretKey).ToBase64String();

            return $"{_qiniuKey.AccessKey}:{signature}";
        }
    }
}